require "sodium"

class AuthD::Request
	def self.perform_login(authd : AuthD::Service, fd : Int32, user : AuthD::User)
		user.date_last_connection = Time.local
		token = user.to_token

		# Change the date of the last connection.
		authd.users_per_uid.update user.uid.to_s, user

		# On successuful connection: store the authenticated user in a hash.
		authd.logged_users[fd] = user.to_public

		Response::Login.new (token.to_s authd.configuration.secret_key), user.uid,
			user.contact.email, user.contact.pending_email
	end

	IPC::JSON.message Login, 0 do
		property login      : String
		property password   : String

		def initialize(@login, @password)
		end

		def to_s(io : IO)
			super io
			io << " (login: #{@login})"
		end

		def handle(authd : AuthD::Service, fd : Int32)
			begin
				user = authd.users_per_login.get @login
			rescue e : DODB::MissingEntry
				# This lack of proper error message is intentional.
				# Let attackers try to authenticate themselves with a wrong login.
				return Response::ErrorInvalidCredentials.new
			end

			# This line is basically just to please the Crystal's type system.
			# No user means DODB::MissingEntry, so it's already covered.
			return Response::ErrorInvalidCredentials.new if user.nil?

			# In case the user hasn't validated his email address (no email address but a token is present),
			# authentication shouldn't be possible.
			if user.contact.email.nil? && user.contact.activation_key
				return Response::ErrorEmailAddressNotValidated.new
			end

			# MIGRATION
			# The migration involves old (broken) hash algorithm.
			# On first connection, the user is authenticated with the old algorithm then a new hash is generated.
			if brkn_hash = user.password_hash_brkn
				# Authenticates the user with its old password hash algo.
				if brkn_hash != authd.obsolete_hash_password @password
					Baguette::Log.error "cannot authenticate the user with his old password hash"
					return Response::ErrorInvalidCredentials.new
				end

				# FYI: there is no need to clone the user since there are no indexes on passwords.
				user.password_hash = authd.hash_password @password    # Adding new password hash.
				user.password_hash_brkn = nil                         # Removing old password hash.
				Baguette::Log.info "updating password hash for #{user.login} to newer algorithm"
				authd.users_per_login.update user

				return AuthD::Request.perform_login authd, fd, user.not_nil!
			end

			pwhash = Sodium::Password::Hash.new
			hash = Base64.decode user.password_hash

			begin
				pwhash.verify hash, @password
			rescue
				return Response::ErrorInvalidCredentials.new
			end

			AuthD::Request.perform_login authd, fd, user.not_nil!
		end
	end
	AuthD.requests << Login

	IPC::JSON.message AuthByToken, 15 do
		property token   : String

		def initialize(@token)
		end

		def handle(authd : AuthD::Service, fd : Int32)
			token_payload = AuthD::Token.from_s authd.configuration.secret_key, token
			user = authd.users_per_uid.get? token_payload.uid.to_s

			return Response::ErrorUserNotFound.new if user.nil?

			AuthD::Request.perform_login authd, fd, user
		end
	end
	AuthD.requests << AuthByToken
end
